﻿using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Id3Lib;
using PlaylistEditorForCentrafuse.GUI;
using System;
using System.Diagnostics;

namespace PlaylistEditorForCentrafuse.Playlist
{
    public class PlaylistAudioFile
    {
        #region Private Members
        private string _filename;
        private List<PlaylistFile> _playlists = new List<PlaylistFile>();
        private ListViewItem _listItem;
        private TagHandler _id3v2 = null;
        private TagHandler _id3v1 = null;
        private uint _audioStart = 0;
        private uint _audioPayloadBytes = 0;
        #endregion

        #region Constructor
        public PlaylistAudioFile(string i_filename)
        {
            _filename = i_filename;
        }
        #endregion

        #region Public Methods
        public bool LoadTags(bool i_updateView)
        {
            bool retVal = false;

            // Tags abrufen
            if (_id3v2 == null)
            {
                // Tags lesen
                FileStream sourceStream = null;
                try
                {
                    // Datei öffnen
                    sourceStream = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Read);

                    // Audio Payload
                    _audioPayloadBytes = (uint)sourceStream.Length;

                    // ID3V2
                    FrameModel tagModel = FrameManager.Deserialize(sourceStream);
                    _id3v2 = new TagHandler(tagModel);

                    // audio starts after the tag
                    _audioStart = (uint)sourceStream.Position;
                    _audioPayloadBytes -= _audioStart;

                    // ID3V1
                    ID3v1 id3v1Tag = new ID3v1();
                    id3v1Tag.Deserialize(sourceStream);
                    _id3v1 = new TagHandler(id3v1Tag.FrameModel);

                    _audioPayloadBytes -= Id3Lib.ID3v1.TagLength;

                    retVal = true;
                }
                catch (TagNotFoundException)
                { 
                    // Ignorieren
                    retVal = true;
                }
                catch (Exception e)
                {
                    MessageBox.Show(_filename + ":\n" + e.Message,
                                    "Fehler beim Lesen der Taginformationen",
                                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                }
                finally
                {
                    if (sourceStream != null)
                    {
                        sourceStream.Close();
                    }
                }

                // Bilder hinzufügen
                if (i_updateView && _id3v2 != null && _id3v2.Picture != null)
                {
                    AddPictureToImagelist();
                }

                // ListView aktualisieren
                if (retVal && i_updateView)
                {
                    UpdateListView();
                }
            }// if (_tagHandler == null)

            return retVal;
        }

        public void WriteTags()
        {
            // ID3v2 schreiben
            if (_id3v2 != null && _id3v2.FrameModel.IsValid)
            {
                _id3v2.FrameModel.UpdateSize();
                uint tagSizeComplete = _id3v2.FrameModel.Header.TagSizeWithHeaderFooter;

                if (tagSizeComplete <= _audioStart)
                {
                    UpdateInSitu(tagSizeComplete);
                }
                else
                {
                    // calculate enough padding to round final file to 2k cluster size.
                    uint minLength = tagSizeComplete + _audioPayloadBytes + Id3Lib.ID3v1.TagLength;
                    uint newLength = ((minLength + 2047) & 0xFFFFF800);    // round up to whole 2k cluster
                    _id3v2.FrameModel.Header.PaddingSize = newLength - minLength;

                    string bakName = Path.ChangeExtension(_filename, "bak");
                    RewriteFile(bakName);
                }

                // Imagelist aktualisieren
                if (_id3v2.Picture != null)
                {
                    AddPictureToImagelist();
                }
                else
                {
                    RemovePictureFromImagelist();
                }
            }// if (_id3v2 != null && _id3v2.FrameModel.IsValid)

            if (_id3v1 != null)
            {
                // now overwrite or append the ID3V1 tag
                WriteID3v1();
            }

            UpdateListView();
        }
        
        public string GetTitleFromFilename()
        {
            string[] splits = Path.GetFileNameWithoutExtension(_filename).Split(new string[] { " - " }, StringSplitOptions.None);

            if (splits.Length > 1)
            {
                return splits[1];
            }
            else
            {
                return "";
            }
        }

        public string GetArtistFromFilename()
        {
            string[] splits = Path.GetFileNameWithoutExtension(_filename).Split(new string[]{" - "}, StringSplitOptions.None);
            
            if (splits.Length > 0)
            {
                return splits[0];
            }
            else
            {
                return "";
            }
        }

        public void AddPlaylist(PlaylistFile i_playlist)
        {
            _playlists.Add(i_playlist);
            UpdateListView();
        }

        public void RemovePlaylist(PlaylistFile i_playlist)
        {
            _playlists.Remove(i_playlist);
            UpdateListView();
        }

        public void UpdateListView()
        {
            if (_listItem != null)
            {
                // Playlists
                _listItem.SubItems[(int)frmEditor.ColumnsAudiofile.NoOfPlaylists].Text = 
                    _playlists.Count.ToString();

                // Tags
                string tagVersion = "";
                if (_id3v1 != null)
                {
                    tagVersion = "ID3v1.1";
                }
                
                bool hasCover = false;
                bool hasLyrics = false;
                if (_id3v2 != null)
                {
                    if (tagVersion != "")
                    {
                        tagVersion += ",";
                    }
                    tagVersion += "ID3v2." + _id3v2.FrameModel.Header.Version.ToString() + 
                                "." + _id3v2.FrameModel.Header.Revision.ToString();

                    hasCover = _id3v2.Picture != null;
                    hasLyrics = string.IsNullOrEmpty(_id3v2.Lyrics) == false;
                }// if (audioFileProps[i_index].ID3v2 != null)
                
                _listItem.SubItems[(int)frmEditor.ColumnsAudiofile.Tags].Text = tagVersion;

                // Cover
                _listItem.SubItems[(int)frmEditor.ColumnsAudiofile.Cover].Text = hasCover ? "Ja" : "Nein";

                // Lyrics
                _listItem.SubItems[(int)frmEditor.ColumnsAudiofile.Lyrics].Text = hasLyrics ? "Ja" : "Nein";

                // Gruppe
                if (_playlists.Count == 1)
                {
                    _listItem.ForeColor = Color.Green;
                }
                else if (_playlists.Count > 1)
                {
                    _listItem.ForeColor = Color.Orange;
                }
                else
                {
                    _listItem.ForeColor = Color.Red;
                }

                // Image
                if (_id3v2 != null && _id3v2.Picture != null)
                {
                    // Albumcover anzeigen
                    _listItem.ImageKey = _filename;
                    //}
                }
                else
                {
                    // Standardimage anzeigen
                    _listItem.ImageIndex = 0;
                }
            }// if (_listItem != null)
        }
        #endregion

        #region Private Methods
        private void AddPictureToImagelist()
        {
            // Cover zu Imagelist hinzufügen
            _listItem.ListView.SmallImageList.Images.Add(_filename, _id3v2.Picture);
            _listItem.ListView.LargeImageList.Images.Add(_filename, _id3v2.Picture);
        }

        private void RemovePictureFromImagelist()
        {
            // Cover von Imagelist entfernen
            _listItem.ListView.SmallImageList.Images.RemoveByKey(_filename);
            _listItem.ListView.LargeImageList.Images.RemoveByKey(_filename);
        }

        private void UpdateInSitu(uint tagSizeComplete)
        {
            // if tag is before the audio, it shouldn't have a footer
            Debug.Assert(_id3v2.FrameModel.Header.Footer == false);

            // calculate enough padding to fill the gap between tag and audio start
            _id3v2.FrameModel.Header.PaddingSize = _audioStart - tagSizeComplete;

            // open source file in readwrite mode
            using (FileStream writeStream = 
                File.Open(_filename, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
            {
                // write header, tags and padding to start of audio
                FrameManager.Serialize(_id3v2.FrameModel, writeStream);

                // verify we've filled the gap exactly
                Debug.Assert(writeStream.Position == _audioStart);
            }
        }

        private void WriteID3v1()
        {
            // open source file in readwrite mode
            using (FileStream writeStream =
                File.Open(_filename, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
            {
                writeStream.Seek(_audioStart + _audioPayloadBytes, SeekOrigin.Begin);
                ID3v1 id3v1 = new ID3v1();
                id3v1.FrameModel = _id3v1.FrameModel;
                id3v1.WriteAtStreamPosition(writeStream);
            }
        }

        private void RewriteFile(string bakFile)
        {
            // generate a temp filename in the target's directory
            string tempName = Path.ChangeExtension(bakFile, "$$$");
            //FileInfo tempFileInfo = new FileInfo(tempName);

            using (FileStream writeStream = 
                File.Open(tempName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read))
            {
                // write an ID3v2 tag to new file
                FrameManager.Serialize(_id3v2.FrameModel, writeStream);

                uint newAudioStart = (uint)writeStream.Position;

                CopyAudioStream(writeStream);

                // if the stream copies without error, update the start of the audio
                _audioStart = newAudioStart;
            }

            // replace the original file, delete new file if fail
            try
            {
				File.Delete(_filename);
                File.Move(tempName, _filename);
                //FileMover.FileMove(tempFileInfo, new FileInfo(_filename), new FileInfo(bakFile));
            }
            catch
            {
                File.Delete(tempName);
                //tempFileInfo.Delete();
                throw;
            }
        }

        private void CopyAudioStream(FileStream writeStream)
        {
            // open original mp3 file stream and seek to the start of the audio
            using (FileStream audio = File.Open(_filename, FileMode.Open, FileAccess.Read, FileShare.Write))
            {
                audio.Seek(_audioStart, SeekOrigin.Begin);

                // Copy mp3 stream
                // this will also copy the original ID3v1 tag if present, 
                // but it's only 128 bytes extra so it won't matter.
                const int size = 4096;
                byte[] bytes = new byte[4096];
                int numBytes;
                while ((numBytes = audio.Read(bytes, 0, size)) > 0)
                    writeStream.Write(bytes, 0, numBytes);
            }
        }
        #endregion

        /////////////////////////////////////////
        // Getters / Setters
        /////////////////////////////////////////
        #region Getters / Setters
       
        public string FileName
        {
            get
            {
                return _filename;
            }
        }

        public List<PlaylistFile> Playlists
        {
            get
            {
                return _playlists;
            }
        }
        
        public ListViewItem ListItem
        {
            set
            {
                _listItem = value;
                UpdateListView();
            }
        }

        public TagHandler ID3v2
        {
            get
            {
                return _id3v2;
            }
        }

        public TagHandler ID3v1
        {
            get
            {
                return _id3v1;
            }
        }
        #endregion
    }
}